{
 "cells": [
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": [
    "点击上方蓝字关注我们\n",
    "\n",
    "![picture.image](https://p3-volc-community-sign.byteimg.com/tos-cn-i-tlddhu82om/3b6943fff0064fb3a696e4e357af6406~tplv-tlddhu82om-image.image?=&rk3s=8031ce6d&x-expires=1744812589&x-signature=hRaSDqNx0M1ojpl4DvNhPoYi7v8%3D)\n",
    "\n",
    "上个月LangChain刚刚发布了正式的0.1稳定版本（没错，是0.1而不是1.0），在版本公告里面首当其冲宣布的最重要更新，是在这个版本里面引入了一个最新库 - **LangGraph。** 这是一个面向当前LLM开发领域最火热的AI Agent开发与控制的开发库，也是LangChain试图用来 **弥补其在Agent开发、特别是复杂的多Agent系统定制方面的不足** 的重大尝试，相信也会成为LangChain在2024升级更新的重点领域！\n",
    "\n",
    "![picture.image](https://p3-volc-community-sign.byteimg.com/tos-cn-i-tlddhu82om/00284b4eca964181a2113923e1df97e9~tplv-tlddhu82om-image.image?=&rk3s=8031ce6d&x-expires=1744812589&x-signature=%2Fp%2FeXgYvN3gt9DWNj%2BR1JQwou5A%3D)\n",
    "\n",
    "我们会用一系列文章深入LangGraph，结合官方例子介绍与剖析其在几个重点Agent方向的应用。\n",
    "\n",
    "*   **LangGraph诞生的动力及设计思想**\n",
    "*   **LangGraph应用：增强的RAG应用**\n",
    "*   **LangGraph应用：自修复代码助手**\n",
    "*   **LangGraph应用：Multi-Agent系统**\n",
    "*   **LangGraph应用：构建Web Agents**\n",
    "\n",
    "由于官方文档较为晦涩，加上LangChain一贯的“重量级”风格。为了更好地帮助深入浅出的理解LangGraph，并照顾到没有LangChain基础的朋友，我们首先来了解一些“预备知识”。\n",
    "\n",
    "PART 01\n",
    "\n",
    "![picture.image](https://p3-volc-community-sign.byteimg.com/tos-cn-i-tlddhu82om/aa6b489b57664ad290f4adfb8a29d21c~tplv-tlddhu82om-image.image?=&rk3s=8031ce6d&x-expires=1744812589&x-signature=XcwREiHmhn2iF7OlnNG8aoRi3Bk%3D)\n",
    "\n",
    "![picture.image](https://p3-volc-community-sign.byteimg.com/tos-cn-i-tlddhu82om/2255992d424e4f24a1633c4a11d7002a~tplv-tlddhu82om-image.image?=&rk3s=8031ce6d&x-expires=1744812589&x-signature=5ygZX2cZKUBC2b%2FEMtpdukC2hg0%3D)\n",
    "\n",
    "预备知识\n",
    "\n",
    "**【LangChain中的链与LCEL】**\n",
    "\n",
    "**Chain（链）** 是LangChain中最核心的概念之一（看名字就知道）。简单的说，就是把自然语言输入、关联知识检索、Prompt组装、可用Tools信息、大模型调用、输出格式化等这些LLM 应用中的常见动作，组装成一个可以运行的“链”式过程。链可以直接调用，也可以用来进一步构建更强大的Agent。\n",
    "\n",
    "**LCEL即LangChain Express Language，** 即LangChain表达语言。这是LangChain提供的一种简洁的、用于组装上述“链”的声明性方式。\n",
    "\n",
    "我们看一个官方使用LCEL“组装”Chain的例子就明白：\n",
    "\n",
    "```\n",
    "\n",
    "          \n",
    "prompt = ChatPromptTemplate.from_template(\"讲一个关于 {topic} 的笑话\")model = ChatOpenAI(model=\"gpt-4\")output_parser = StrOutputParser()chain = prompt | model | output_parser\n",
    "          \n",
    "#调用chainchain.invoke({\"topic\": \"冰淇淋\"})\n",
    "      \n",
    "\n",
    "```\n",
    "\n",
    "这个官方的例子中，把提示（prompt）、大模型（model）、输出解析（output\\_parser）几个组件使用管道符号“｜”链接在一起，上个组件的输出作为下一个组件的输入，一起形成了一个链。\n",
    "\n",
    "对于最常见的RAG应用来说，使用LCEL也无非是在此之上增加一个检索相关文档的动作，类似：\n",
    "\n",
    "```\n",
    "\n",
    "        \n",
    "            \n",
    "\n",
    "          chain = setup\\_and\\_retrieval | prompt | model | output\\_parser\n",
    "        \n",
    "      \n",
    "\n",
    "```\n",
    "\n",
    "这里很清晰地看到一个简单的RAG应用处理过程：检索关联文档 => 组装Prompt => 调用大模型 => 输出处理。\n",
    "\n",
    "最后总结一下： **LCEL就是LangChain提供用来组装Chain的一种简单表示方式。用这种方式组装链，可以自动获得诸如批量、流输出、并行、异步等一系列能力；而且链可以进一步通过LCEL组装成更复杂的链与Agent。**\n",
    "\n",
    "**【LCEL构建与调度Agent】**\n",
    "\n",
    "那么如何用LCEL来创建一个AI Agent并调度运行呢？以最常见的React（推理&行动）范式的Agent来说，相对于Chain需要扩展的能力有：\n",
    "\n",
    "*   **增加工具使用能力** 。这体现在Prompt中需要注入可用工具信息，并能自动调用工具获得结果。\n",
    "*   **增加“循环”能力。** Agent的运行通常需要多次Reason（推理）-Act（行动）的反复与循环，直到完成任务。\n",
    "\n",
    "以LCEL来组装并创建运行一个Agent的简单过程如下：\n",
    "\n",
    "```\n",
    "\n",
    "        \n",
    "            \n",
    "\n",
    "          \"\"\"  \n",
    "定义Agent需要使用的Tools  \n",
    "\"\"\"  \n",
    "@tool  \n",
    "def search(query: str) -> str:  \n",
    "  \"\"\"此处省略\"\"\"  \n",
    "  \n",
    "\"\"\"  \n",
    "LCEL创建一个Agent，与Chain类似  \n",
    "\"\"\"  \n",
    "agent = (  \n",
    "    {input:{输入信息}, agent\\_scratchpad:{中间步骤}}  \n",
    "    | prompt  \n",
    "    | model  \n",
    "    | AgentOutputParser()  \n",
    ")  \n",
    "  \n",
    "\"\"\"  \n",
    "注意：Agent需要使用agent\\_executor调用，以增加上述两个能力  \n",
    "\"\"\"  \n",
    "agent\\_executor = AgentExecutor(agent=agent, tools=tool\\_list, verbose=True)  \n",
    "agent\\_executor.invoke({\"input\": \"whats the weather in New york?\"})\n",
    "        \n",
    "      \n",
    "\n",
    "```\n",
    "\n",
    "注意到，相对于Chain.invoke()直接运行，这里的 **Agent\\_executor的作用就是为了能够实现多次循环ReAct的动作，以最终完成任务。**\n",
    "\n",
    "**【什么是图（Graph）】**\n",
    "\n",
    "图是计算机科学中的一种数据结构。大部分人可能都接触过一些基本的数据结构，比如队列（Queue）、堆栈（Stack）、链表（List）或者树（Tree）等，图（Graph）也是其中的一种相对复杂的数据结构。我们无意在此普及图的数据结构知识，你只需要了解的图的几个基本知识：\n",
    "\n",
    "*   **图是表示多个元素及其之间关系的一种结构** 。其特点是，任何两个元素之间都可以直接发生联系，所以适合表达更复杂的元素关系。\n",
    "*   图的基本表示就是N个元素（节点/顶点）及这些元素之间的关系（边）的集合。\n",
    "\n",
    "![picture.image](https://p3-volc-community-sign.byteimg.com/tos-cn-i-tlddhu82om/f1cca9cd649c4b219bdedbc9ba3be5c9~tplv-tlddhu82om-image.image?=&rk3s=8031ce6d&x-expires=1744812589&x-signature=F9N8YtbSUKTkUjXzQUt0vizYXm8%3D)\n",
    "\n",
    "*   **有向无环图（Directed Acyclic Graph，DAG）** ：有向指的是图中的“边”有方向；无环指的是无法从某个节点经过若干“边”返回这个节点。\n",
    "\n",
    "其 他的一些图 的理论，包括不同类型图的存储结构、相关算法等 ，这里对理解LangGraph无关紧要。\n",
    "\n",
    "PART 02\n",
    "\n",
    "![picture.image](https://p3-volc-community-sign.byteimg.com/tos-cn-i-tlddhu82om/aa6b489b57664ad290f4adfb8a29d21c~tplv-tlddhu82om-image.image?=&rk3s=8031ce6d&x-expires=1744812589&x-signature=XcwREiHmhn2iF7OlnNG8aoRi3Bk%3D)\n",
    "\n",
    "![picture.image](https://p3-volc-community-sign.byteimg.com/tos-cn-i-tlddhu82om/2255992d424e4f24a1633c4a11d7002a~tplv-tlddhu82om-image.image?=&rk3s=8031ce6d&x-expires=1744812589&x-signature=5ygZX2cZKUBC2b%2FEMtpdukC2hg0%3D)\n",
    "\n",
    "LangGraph的驱动力\n",
    "\n",
    "即然上文介绍的LCEL已经很强大，但是为什么还需要LangGraph呢？基于LCEL构建的Chain与Agent又存在哪些不足呢？\n",
    "\n",
    "\\*\\*\\* 链（Chain）：无法满足在循环中调用LLM以完成任务。\\*\\*\n",
    "\n",
    "上文中，我们可以轻易地使用LCEL来快速创建一个链，但是很显然的一个问题是：如果我们把链中的组件想象成Graph中的节点，组件之间的联系想象成Graph中的边，那么这个链就是一个有向无环图(DAG）。 **即在一次Chain运行中，一个调用节点无法重复/循环进入。**\n",
    "\n",
    "![picture.image](https://p3-volc-community-sign.byteimg.com/tos-cn-i-tlddhu82om/48580e4f438249d58eea7f641951e8bf~tplv-tlddhu82om-image.image?=&rk3s=8031ce6d&x-expires=1744812589&x-signature=rfIvRiKmd1WtWAWb8Zy418XtArg%3D)\n",
    "\n",
    "那么为什么需要将循环引入运行时呢？考虑一个增强的RAG应用:\n",
    "\n",
    "![picture.image](https://p3-volc-community-sign.byteimg.com/tos-cn-i-tlddhu82om/3f2238a76455419f973ef262aeab0250~tplv-tlddhu82om-image.image?=&rk3s=8031ce6d&x-expires=1744812589&x-signature=aS2Y3nvwHy0Xi70H65HuekIsCi4%3D)\n",
    "\n",
    "在这个RAG应用设计中，我们可以对语义检索出来的关联文档（上下文）进行评估：如果评估的文档质量很差，可以对检索的问题进行重写（Rewrite，比如把输入的问题结合对话历史用更精确的方式来表达），并把重写结果重新交给检索器，检索出新的关联文档，这样有助于获得更精确的结果。\n",
    "\n",
    "**这里把Rewrite的问题重新交给检索器，就是一个典型的“循环”动作。 而在目前LangChain的简单链中是无法支持的。**\n",
    "\n",
    "其他一些典型的依赖“循环”的场景包括：\n",
    "\n",
    "*   **代码生成时的自我纠正：** 当借助LLM自动生成软件代码时，根据代码执行的结果进行自我反省，并要求LLM重新生成代码。\n",
    "*   **Web访问自动导航** ：每当进入下一界面时，需要借助多模态模型来决定下一步的动作（点击、滚动、输入等），直至完成导航。\n",
    "\n",
    "\\*\\*\\* AgentExecutor：尽管支持“循环”，但缺乏精确控制能力。\\*\\*\n",
    "\n",
    "那么，如果我们需要在循环中调用LLM能力，就需要借助于AgentExecutor。其调用的过程主要就是两个步骤：\n",
    "\n",
    "1.  通过大模型来决定采取什么行动，使用什么工具，或者向用户输出响应（如运行结束时）；\n",
    "2.  执行1步骤中的行动，比如调用某个工具，并把结果继续交给大模型来决定，即返回步骤1；\n",
    "\n",
    "这里的AgentExecute存在的问题是： **过于黑盒，所有的决策过程隐藏在AgentExecutor背后，缺乏更精细的控制能力，在构建复杂Agent的时候受限** 。这些精细化的控制要求比如：\n",
    "\n",
    "*   某个Agent要求首先强制调用某个Tool\n",
    "*   在 Agent运行过程中增加人机交互步骤\n",
    "*   能够灵活更换Prompt或者背后的LLM\n",
    "*   多Agent（Multi-Agent）智能体构建的需求，即多个Agent协作完成任务的场景支持。（这也是Langchain相对于竞争对手Autogen等最薄弱的能力之一，也是众多开发者千呼万唤的特性）\n",
    "\n",
    "所以，让我们简单总结LangGraph诞生的动力： **LangChain简单的链（Chain）不具备“循环”能力；而**AgentExecutor调度的Agent\\*\\* 运行又过于“黑盒”。因此需要一个具备更精细控制能力的框架来支持更复杂场景的LLM应用。\\*\\*\n",
    "\n",
    "PART 03\n",
    "\n",
    "![picture.image](https://p3-volc-community-sign.byteimg.com/tos-cn-i-tlddhu82om/aa6b489b57664ad290f4adfb8a29d21c~tplv-tlddhu82om-image.image?=&rk3s=8031ce6d&x-expires=1744812589&x-signature=XcwREiHmhn2iF7OlnNG8aoRi3Bk%3D)\n",
    "\n",
    "![picture.image](https://p3-volc-community-sign.byteimg.com/tos-cn-i-tlddhu82om/2255992d424e4f24a1633c4a11d7002a~tplv-tlddhu82om-image.image?=&rk3s=8031ce6d&x-expires=1744812589&x-signature=5ygZX2cZKUBC2b%2FEMtpdukC2hg0%3D)\n",
    "\n",
    "LangGraph的设计思想\n",
    "\n",
    "LangGraph并非一个独立于Langchain的新框架，它是基于Langchain之上构建的一个扩展库，可以与Langchain现有的链、LCEL等无缝协作。LangGraph能够协调多个Chain、Agent、Tool等共同协作来完成输入任务，支持LLM调用“循环”以及Agent过程的更精细化的控制。\n",
    "\n",
    "**LangGraph的实现方式是把之前基于AgentExecutor的黑盒调用过程用一种新的形式来构建：状态图（StateGraph）。** **把基于LLM的任务（比如RAG、代码生成等）细节用Graph进行精确的定义（定义图的节点与边），最后基于这个图来编译生成应用；在任务运行过程中，维持一个中央状态对象(state)，会根据节点的跳转不断更新，状态包含的属性可自行定义。**\n",
    "\n",
    "我们用官方的一个增强的RAG应用的Graph来帮助理解：\n",
    "\n",
    "![picture.image](https://p3-volc-community-sign.byteimg.com/tos-cn-i-tlddhu82om/6a3001a1f142491b8dc023fb04b60984~tplv-tlddhu82om-image.image?=&rk3s=8031ce6d&x-expires=1744812589&x-signature=mqkf%2FHRAEu46YFXXIkaCFVx87mE%3D)\n",
    "\n",
    "这个Graph中体现了LangGraph的几个基本概念：\n",
    "\n",
    "*   **StateGraph** ：这是代表整个状态图的基础类。\n",
    "*   **Nodes** ：节点。在有了图之后，可以向图中添加节点，节点通常是一个可调用的函数、一个可运行的Chain或者Agent。有一个特殊的节点叫END，进入这个节点，代表运行结束。\n",
    "\n",
    "在上图中，推理函数调用、调用检索器、生成响应内容、问题重写等都是其中的任务节点。\n",
    "\n",
    "*   **Edges** ：边。有了节点后，需要向图中添加边，边代表从上一个节点跳转到下一个节点的关系。目前有三种类型的边：\n",
    "\n",
    "*   **Starting Edge** ：一种特殊的边。用来定义任务运行的开始节点，所以它没有上一个节点。\n",
    "*   **Normal Edge** ：普通边。代表上一个节点运行完成后立即进入下一个节点。比如在调用Tools后获得结果后，立刻进入LLM推理节点。\n",
    "*   **Conditional Edge** ：条件边。代表上一个节点运行完成后，需要根据条件跳转到某个节点，因此这种边不仅需要上游节点、下游节点，还需要一个条件函数，根据条件函数的返回来决定下游节点。\n",
    "\n",
    "在上图中，Check Relevance就是一个条件边，它的上游节点是检索相关文档，条件函数是判断文档是否相关，如果相关，则进入下游节点【产生回答】；如果不相关，则进入下游节点【重写输入问题】。\n",
    "\n",
    "在构建好StateGraph，并增加Node和Edge后，可以通过compile编译成可运行的应用：\n",
    "\n",
    "**app = graph.compile()**\n",
    "\n",
    "接下来你就可以调用这个app来完成你的任务。\n",
    "\n",
    "PART 04\n",
    "\n",
    "![picture.image](https://p3-volc-community-sign.byteimg.com/tos-cn-i-tlddhu82om/aa6b489b57664ad290f4adfb8a29d21c~tplv-tlddhu82om-image.image?=&rk3s=8031ce6d&x-expires=1744812589&x-signature=XcwREiHmhn2iF7OlnNG8aoRi3Bk%3D)\n",
    "\n",
    "![picture.image](https://p3-volc-community-sign.byteimg.com/tos-cn-i-tlddhu82om/2255992d424e4f24a1633c4a11d7002a~tplv-tlddhu82om-image.image?=&rk3s=8031ce6d&x-expires=1744812589&x-signature=5ygZX2cZKUBC2b%2FEMtpdukC2hg0%3D)\n",
    "\n",
    "LangGraph构建基础Agent\n",
    "\n",
    "我们可以粗暴的认为LangGraph就是把现在黑盒的AgentExecutor揉碎掰开，允许你定义内部的细节结构（用图的方式），从而实现更强大的功能。 那么我们当然可以用LangGraph来重新实现原来的AgentExecutor，即实现一个最基础的ReAct范式的Agent应用。\n",
    "\n",
    "对应的Graph如下：\n",
    "\n",
    "![picture.image](https://p3-volc-community-sign.byteimg.com/tos-cn-i-tlddhu82om/5f31cd9220804fc8abe0e7313623b298~tplv-tlddhu82om-image.image?=&rk3s=8031ce6d&x-expires=1744812589&x-signature=cA4YkSH2sScD%2BGmxiTCbijx3Sc0%3D)\n",
    "\n",
    "简单的实现代码如下（省略了部分细节）：\n",
    "\n",
    "```\n",
    "\n",
    "        \n",
    "            \n",
    "\n",
    "          # 定义一个Graph，传入state定义（参考上图state属性）  \n",
    "workflow = StateGraph(AgentState)  \n",
    "  \n",
    "# 两个节点  \n",
    "  \n",
    "#节点1: 推理节点，调用LLM决定action，省略了runreason细节  \n",
    "workflow.add\\_node(\"reason\", run\\_reason)  \n",
    "  \n",
    "#节点2: 行动节点，调用tools执行action，省略executetools细节  \n",
    "workflow.add\\_node(\"action\", execute\\_tools)  \n",
    "  \n",
    "#入口节点：总是从推理节点开始  \n",
    "workflow.set\\_entry\\_point(\"reason\")  \n",
    "  \n",
    "#条件边：根据推理节点的结果决定下一步  \n",
    "workflow.add\\_conditional\\_edges(  \n",
    "    \"reason\",  \n",
    "    should\\_continue, #条件判断函数（自定义，根据状态中的推理结果判断）  \n",
    "    {  \n",
    "        \"continue\": \"action\", #如果条件函数返回continue，进action节点  \n",
    "        \"end\": END, #如果条件函数返回end，进END节点  \n",
    "    },  \n",
    ")  \n",
    "  \n",
    "#普通边：action结束后，总是返回reason  \n",
    "workflow.add\\_edge(\"action\", \"reason\")  \n",
    "  \n",
    "#编译成app  \n",
    "app = workflow.compile()  \n",
    "  \n",
    "#可以调用app了，并使用流式输出  \n",
    "inputs = {\"input\": \"you task description\", \"chat\\_history\": []}  \n",
    "for s in app.stream(inputs):  \n",
    "    print(list(s.values())[0])  \n",
    "    print(\"----\")\n",
    "        \n",
    "      \n",
    "\n",
    "```\n",
    "\n",
    "代码中的注释对graph构建的细节做了解释。显然，这要比简单的使用agentExecutor要复杂的多，但同时也展示了LangGraph在构建LLM应用时强大的控制能力： **通过Graph的定义，可以对一个LLM应用的处理过程进行非常细节的编排设计，从而满足大量复杂场景的AI Agent应用。**\n",
    "\n",
    "由于LangGraph刚推出不久，一些细节与易用性在后期也会不断完善。比如未来是否会提供更直观的定义界面等，也值得期待。 在后续的文章中，我们将逐渐实践几个代表性场景下的LangGraph的应用，比如代码助手，自省式RAG，多Agent应用等，敬请期待。\n",
    "\n",
    "![picture.image](https://p3-volc-community-sign.byteimg.com/tos-cn-i-tlddhu82om/73b2f054bb3f440888004ee237b8a535~tplv-tlddhu82om-image.image?=&rk3s=8031ce6d&x-expires=1744812589&x-signature=yyLMVGimMDBTwIs%2BNKSDVWfYhzM%3D)\n",
    "\n",
    "END\n",
    "\n",
    "**点击下方关注我，不迷路**\n",
    "\n",
    "**交流请识别以下名片并说明来源**\n",
    "\n",
    "![picture.image](https://p3-volc-community-sign.byteimg.com/tos-cn-i-tlddhu82om/cbf8166546a642dcbbef14fa31fbe509~tplv-tlddhu82om-image.image?=&rk3s=8031ce6d&x-expires=1744812589&x-signature=boVNWiI2eufjBLXgWBxMr597tM4%3D)\n",
    "\n",
    "  \n",
    "\n",
    "本文转自 [https://developer.volcengine.com/articles/7370376472717951026#article\\_title](https://developer.volcengine.com/articles/7370376472717951026#article_title)，如有侵权，请联系删除。"
   ],
   "id": "5625c567f6af797"
  },
  {
   "metadata": {},
   "cell_type": "code",
   "outputs": [],
   "execution_count": null,
   "source": "",
   "id": "602275c597c99b46"
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
